﻿//namespace Common {
//    public class Shader {
//        public Shader() {
//        }

//        protected override void OnDispose() {
//            //throw new System.NotImplementedException();
//        }
//        public static Shader CreateFromFile(string vertexFileName, string fragmentFileName) {
//            return null;
//        }
//    }
//}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenTK;
using OpenTK.Graphics.OpenGL4;
namespace Common {
    public class Shader : GlDisposableObjectBase {
        public class Consts {
            public const uint INVALID_MATERIAL = 0xffff_ffff;
            public const uint INVALID_UNIFORM_LOCATION = 0xffff_ffff;
        }
        public static Shader CreateBySource(string vertex, string fragment) {
            //Shader shader = new Shader();
            //shader.Compile(vertex, fragment);
            //return shader;
            var shader = new Shader();
            shader.Compile(vertex, fragment);
            return shader;
        }
        public static Shader CreateFromFile(string vertexFileName, string fragmentFileName) {
            string vertexSource = File.ReadAllText(vertexFileName, Encoding.UTF8);
            string fragmentSource = File.ReadAllText(fragmentFileName, Encoding.UTF8);
            return CreateBySource(vertexSource, fragmentSource);
        }
        private Shader() {
        }
        public Shader(string vertexFileName, string fragmentFileName, string geometryFilename = null) {
            try {
                //string vertexSource = File.ReadAllText(vertexFileName, Encoding.UTF8);
                //string fragmentSource = File.ReadAllText(fragmentFileName, Encoding.UTF8);
                //Compile(vertexSource, fragmentSource);
                CompilePath(vertexFileName, fragmentFileName, geometryFilename);
            }
            catch (Exception ex) {
                GC.SuppressFinalize(this);
                throw ex;
            }

        }
        //public int testId { get; } = 2;
        bool complied = false;

        int mProgramId;
        public int ID {
            get {
                if (complied) {
                    return mProgramId;
                }
                else {
                    throw new EngineException("shader not compiled");
                }
            }
            private set { mProgramId = value; }
        }
        public bool AllowShaderError { get; set; }

        public void Use() {
            GL.UseProgram(this.ID);
        }
        public int GetUniformLocation(string name) {
            int Location = GL.GetUniformLocation(this.ID, name);

            if ((uint)Location == Consts.INVALID_UNIFORM_LOCATION) {
                throw new Exception($"Warning! Unable to get the location of uniform {name}");
            }

            return Location;
        }
        public void SetBool(string name, bool value) {
            var location = GetUniformLocation(name);
            GL.Uniform1(location, value ? 1 : 0);
        }
        public void SetInt(string name, int value) {
            var location = GetUniformLocation(name);
            GL.Uniform1(location, value);
        }
        public void SetFloat(string name, float value) {
            var location = GetUniformLocation(name);
            GL.Uniform1(location, value);
        }

        public void SetMatrix4(string name, ref Matrix4 value) {
            var location = GetUniformLocation(name);
            GL.UniformMatrix4(location, false, ref value);
        }

        public void SetMatrix4(string name, Matrix4 value) {
            var location = GetUniformLocation(name);
            GL.UniformMatrix4(location, false, ref value);
        }

        public void SetMatrix3(string name, Matrix3 value) {
            var location = GetUniformLocation(name);
            GL.UniformMatrix3(location, false, ref value);
        }

        public void SetMatrix3(string name, ref Matrix3 value) {
            var location = GetUniformLocation(name);
            GL.UniformMatrix3(location, false, ref value);
        }

        public void SetVector4(string name, Vector4 value) {
            var location = GetUniformLocation(name);
            GL.Uniform4(location, value);
        }

        public void SetVector4(string name, float x, float y, float z, float w) {
            SetVector4(name, new Vector4(x, y, z, w));
        }

        public void SetVector3(string name, Vector3 value) {
            var location = GetUniformLocation(name);
            GL.Uniform3(location, value);
        }

        public void SetVector3(string name, float x, float y, float z) {
            SetVector3(name, new Vector3(x, y, z));
        }

        public void SetVector2(string name, Vector2 value) {
            var location = GetUniformLocation(name);
            GL.Uniform2(location, ref value);
        }

        public void SetVector2(string name, float x, float y) {
            SetVector2(name, new Vector2(x, y));
        }

        public void Compile(string vertexSource, string fragmentSource, string geometrySource = null) {
            this.ID = LoadShader(vertexSource, fragmentSource, geometrySource);
            complied = true;
        }

        public void CompilePath(string vertexPath, string fragmentPath, string geometryPath = null) {
            string vertexSource = File.ReadAllText(vertexPath, Encoding.UTF8);
            string fragmentSource = File.ReadAllText(fragmentPath, Encoding.UTF8);
            string geometrySource = null;
            if (geometryPath != null) {
                geometrySource = File.ReadAllText(geometryPath, Encoding.UTF8);
            }
            Compile(vertexSource, fragmentSource, geometrySource);
        }

        //bool disposed = false;

        static int LoadShader(string vertexSource, string fragmentSource, string geometrySource = null, bool ignoreError = false) {
            int vertexShaderId = 0, fragmentShaderId = 0, geometryId = 0;
            try {
                vertexShaderId = CompileShader(vertexSource, ShaderType.VertexShader, ignoreError, "vertex shader error:");
                fragmentShaderId = CompileShader(fragmentSource, ShaderType.FragmentShader, ignoreError, "fragment shader error:");
                if (geometrySource != null) {
                    geometryId = CompileShader(geometrySource, ShaderType.GeometryShader, ignoreError, "geometry shader error:");
                }
            }
            catch (Exception ex) {
                GL.DeleteShader(vertexShaderId);
                GL.DeleteShader(fragmentShaderId);
                GL.DeleteShader(geometryId);
                throw ex;
            }

            //int fragmentShaderId = LoadShader(fragmentSource, ShaderType.FragmentShader, ignoreError);
            int programId = GL.CreateProgram();
            GL.AttachShader(programId, fragmentShaderId);
            GL.AttachShader(programId, vertexShaderId);
            if (geometrySource != null) {
                GL.AttachShader(programId, geometryId);
            }
            GL.LinkProgram(programId);
            //GL.UseProgram(programId);
            GL.DetachShader(programId, fragmentShaderId);
            GL.DetachShader(programId, vertexShaderId);
            if (geometrySource != null) {
                GL.DetachShader(programId, geometryId);
            }
            GL.DeleteShader(fragmentShaderId);
            GL.DeleteShader(vertexShaderId);
            if (geometrySource != null) {
                GL.DeleteShader(geometryId);
            }
            return programId;
        }

        private static int CompileShader(string vertexSource, ShaderType shaderType, bool ignoreError, string message) {
            try {
                int ShaderId = LoadShader(vertexSource, shaderType, ignoreError);
                return ShaderId;
            }
            catch (Exception ex) {
                Console.WriteLine(ex.Message);

                throw new EngineException(message + ex.Message);
            }
        }

        private static int LoadShader(string source, ShaderType type, bool ignoreError) {
            string compileInfo;
            int statusCode;
            int shaderId = GL.CreateShader(type);
            GL.ShaderSource(shaderId, source);
            GL.CompileShader(shaderId);

            GL.GetShaderInfoLog(shaderId, out compileInfo);
            GL.GetShader(shaderId, ShaderParameter.CompileStatus, out statusCode);
            if (statusCode != 1) {
                if (!ignoreError) {
                    throw new ApplicationException(compileInfo);
                }
            }
            return shaderId;
        }



        protected override void OnDispose() {
            GL.UseProgram(0);
            GL.DeleteProgram(ID);
        }

   

    }

}
